<?php

namespace System\library\mvc\controller;

use System\library\Http;
use System\library\Logger;
use System\library\config\Config;
use System\library\pattern\Singleton;
use System\library\utility\Validate;

/**
 * <b>Class</b> :        Dispatcher<br>
 * <b>Fonction</b> :     Dispatch controller and run
 * <br>
 * @version              1.0
 * @author               Dread <dreadlokeur@gmail.com>
 * @license              Licence publique générale GNU http://www.gnu.org/licenses/gpl.html
 * @copyright            Copyright 2011 - MidichloriansPHP and contributors
 * @package              System
 * @subpackage           library\mvc\controller
 *
 * @see System\library\pattern\Singleton
 */
class Dispatcher extends Singleton {

//TODO CHECK IF APPLICATION WAS RUNNED BEFORE RUN CONTROLLER DISPATCHER
    private $_config = null;
    private $_isRunned = false;
    private $_controllersDir;
    private $_controllersNamespace = 'Controllers';
    // url Keys
    private $_urlKeyControllerDir = 'dir';
    private $_urlKeyController = 'controller';
    private $_urlKeyAction = 'action';
    //index Url Parameter
    private $_urlIndex = array(
        'controllerDir' => 'index',
        'controller' => 'IndexController',
        'action' => 'indexAction',
        'callMethods' => null
    );
    // Config of controller
    private $_controller;
    private $_action;
    private $_callMethods = null;
    private $_isConfiguredController = false;
    private $_is404Controller = false;
    // Default parameters
    private $_indexController = 'IndexController';
    private $_indexAction = 'indexAction';
    private $_interface = array(
        'mustBeImplementInterface' => true,
        'interfaceName' => 'MidichloriansPHP\mvc\controller\IController',
    );
    // définit l'autocalling des methodes display et indexAction(si aucune methode est démandé...)
    private $_autoCallIndexAction = true;
    private $_autoCallDisplay = true;

    public function setDispatcherConfigFile($dispatcherFileConf) {
        if (!file_exists($dispatcherFileConf))
            throw new \Exception('Le fichier de configuration du Dispatcher : "' . $dispatcherFileConf . '" n\'existe pas');
        $this->_config = @simplexml_load_file($dispatcherFileConf);

        if ($this->_config === null || $this->_config === false)
            throw new \Exception('Le fichier de configuration du Dispatcher : "' . $dispatcherFileConf . '" n\'est pas un fichier XML valid');

        $this->_setDispatcherParameters();
        $this->_setRewrittingRules();
        $this->_setUrlsParameters();
        return $this;
    }

    public function setControllersDir($dir) {
        if (!is_dir($dir))
            throw new \Exception('Le dossier contenant les controllers est invalid');

        $this->_controllersDir = $dir;
        return $this;
    }

    public function setControllersNamespace($controllersNamespace) {
        $this->_controllersNamespace = $controllersNamespace;
        return $this;
    }

    public function run() {
        if ($this->_config === null)
            throw new \Exception('Le Dispatcher n\'a pas été correctement configuré');
        if ($this->_isRunned)
            throw new \Exception('Dispatcher is already runned');

        Logger::getInstance()->debug('Dispatcher was runned');
        $this->_isRunned = true;

        //get Key Value into Url
        $urlKeyControllerDirValue = Http::getQuery($this->_urlKeyControllerDir, '');
        $urlKeyControllerValue = Http::getQuery($this->_urlKeyController, '');
        $urlKeyActionValue = Http::getQuery($this->_urlKeyAction, '');

        // URL VIDE ou Index URL
        if (($urlKeyControllerDirValue == '' || $urlKeyControllerDirValue == $this->_urlIndex['controllerDir']) && ($urlKeyControllerValue == '' || $urlKeyControllerValue == $this->_urlIndex['controller']) && ($urlKeyActionValue == '' || $urlKeyActionValue == $this->_urlIndex['action']))
            $this->_runController($this->_urlIndex['controllerDir'], $this->_urlIndex['controller'], $this->_urlIndex['action'], true, $this->_urlIndex['callMethods']);
        // URL NON VIDE ou NON Index URL
        else
        //TODO get vars into url
            $this->_runController($urlKeyControllerDirValue, $urlKeyControllerValue, $urlKeyActionValue);
    }

    public function showMaintenance() {
        if ($this->_config !== null && isset($this->_config->urlMaintenance))
            $this->_runController($this->_config->urlMaintenance->controllerDir, $this->_config->urlMaintenance->controller, $this->_config->urlMaintenance->action, true, $this->_config->urlMaintenance->callMethods);
        else {
            if (!headers_sent()) {
                header('HTTP/1.0 404 Not Found');
                header('Status: 404 Not Found');
            }
        }
    }

    public function show500() {
        if ($this->_config !== null && isset($this->_config->url500)) {
            $this->_runController($this->_config->url500->controllerDir, $this->_config->url500->controller, $this->_config->url500->action, true, $this->_config->url500->callMethods);
        } else {
            if (!\headers_sent()) {
                \header('HTTP/1.0 500 Internal Server Error');
                \header('Status: 500 Internal Server Error');
            }
        }
    }

    protected function __construct() {

    }

    private function _setDispatcherParameters() {
        //Paramètres des key de l'url
        if (isset($this->_config->urlKeyControllerDir)) {
            if (!Validate::isVariableName((string) $this->_config->urlKeyControllerDir))
                throw new \Exception('La key "urlKeyControllerDir" doit être un nom de variable valid');

            $this->_urlKeyControllerDir = (string) $this->_config->urlKeyControllerDir;
        }
        if (isset($this->_config->urlKeyControllerName)) {
            if (!Validate::isVariableName((string) $this->_config->urlKeyControllerName))
                throw new \Exception('La key "urlKeyControllerName" doit un nom de variable valid');

            $this->_urlKeyController = (string) $this->_config->urlKeyControllerName;
        }
        if (isset($this->_config->urlKeyActionName)) {
            if (!Validate::isVariableName((string) $this->_config->urlKeyActionName))
                throw new \Exception('La key "urlKeyControllerDir" doit être un nom de variable valid');

            $this->_urlKeyAction = (string) $this->_config->urlKeyActionName;
        }
        //Disable autoCalling: display or indexAction
        if (isset($this->_config->dispatcher['autoCallIndexAction']) && !(int) $this->_config->dispatcher['autoCallIndexAction'])
            $this->_autoCallIndexAction = false;
        if (isset($this->_config->dispatcher['autoCallDisplay']) && !(int) $this->_config->dispatcher['autoCallDisplay'])
            $this->_autoCallDisplay = false;
    }

    private function _setRewrittingRules() {
        //TODO get all rewritting rules defined
    }

    private function _setUrlsParameters() {
        // URL INDEX
        if (isset($this->_config->urlIndex)) {
            // directory
            if (isset($this->_config->urlIndex->controllerDir))
                $this->_urlIndex['controllerDir'] = (string) $this->_config->urlIndex->controllerDir;

            // Controller
            if (isset($this->_config->urlIndex->controller))
                $this->_urlIndex['controller'] = (string) $this->_config->urlIndex->controller;

            // Action
            if (isset($this->_config->urlIndex->action))
                $this->_urlIndex['action'] = (string) $this->_config->urlIndex->action;

            // CallMethods
            if (isset($this->_config->urlIndex->callMethods))
                $this->_urlIndex['callMethods'] = $this->_config->urlIndex->callMethods;
        }
        //TODO get All Defined URLs in conf file and set parameters
    }

    private function _runController($controllerDir, $controller, $action, $tryConfigureController = true, $callMethods = null, $vars = array()) {
        // cast
        $controllerDir = (string) $controllerDir;
        $controller = (string) $controller;
        $action = (string) $action;
        if (!is_object($callMethods) && $callMethods !== null)
            $callMethods = (object) $callMethods;

        // On essaye de configurer le controller, si il a pu etre configurer alors on demande de le run avec les paramètre de configuration (recursif)
        if ($tryConfigureController) {
            $this->_configureController($controllerDir, $controller, $action);
            if ($this->_isConfiguredController) {
                //$callMethods = $this->_mergeCallMethods($this->_callMethods, $callMethods);
                $this->_runController($controllerDir, $this->_controller, $this->_action, false, $this->_callMethods, $vars);
                return;
            }
        }
        // Action ou/et controller vide, alors on définit les valeurs de base: IndexController, indexAction
        $controller = ($controller == '') ? $this->_indexController : $controller;
        $action = ($action == '') ? $this->_indexAction : $action;

        // Verification du controller (exists, paramètre correct, methode demandé existes..) et recuperation de l'instance ReflectionClass
        if (!($inst = $this->_checkingController($controllerDir, $controller, $action)))
            return;
        else {
            Logger::getInstance()->debug('Run controller : ' . $controllerDir . '\\' . $controller);
            // Creation de l'instance du controller
            $ctrl = $inst->newInstance();

            // On appelle la méthode définit en action
            if ($action != $this->_indexAction || $this->_autoCallIndexAction) {
                Logger::getInstance()->debug('Controller call action : ' . $action);
                $ctrl->$action();
            }
            // Auto calling methods...
            if ($callMethods) {
                foreach ($callMethods->method as $method) {
                    $parameters = array();
                    if (!method_exists($ctrl, $method['name']))
                        throw new \Exception('La methode : "' . $method['name'] . '" du controller "' . $controllerDir . '\\' . $controller . '" n\'existe pas');
                    if ($method->parameter) {
                        foreach ($method->parameter as $parameter) {
                            if (count($vars) > 0) {
                                // Closure function for formating vars keys
                                $fcn = function($array) {
                                            $keys = array_keys($array);
                                            foreach ($keys as &$key)
                                                $key = '[[' . $key . ']]';
                                            return $keys;
                                        };
                                // Replacing parameters
                                $parameter = str_replace($fcn($vars), $vars, $parameter);
                            }
                            $parameters[] = $parameter;
                        }
                    }
                    Logger::getInstance()->debug('Controller call method : ' . (string) $method['name'] . ' with ' . count($parameters) . ' parameters');
                    // Call method with parameters
                    call_user_func_array(array($ctrl, (string) $method['name']), $parameters);
                }
            }
            // Display
            if ($this->_autoCallDisplay) {
                Logger::getInstance()->debug('Controller displayed');
                $ctrl->display();
            }
        }
    }

    private function _configureController($controllerDir, $controller, $action) {
        if (file_exists($fileConfController = $this->_controllersDir . $controllerDir . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.xml'))
            $configController = @simplexml_load_file($fileConfController);

        // On définit le controller, l'action et les callMethodes en fonction du fichier xml définit dans le path du controller
        if (isset($configController) && $configController !== null) {
            // Controller
            if ($controller == '') {
                $this->_controller = (isset($configController->indexController)) ? $configController->indexController : $this->_indexController;
            }else
                $this->_controller = $controller;

            //Action
            if ($action == '')
                $this->_action = (isset($configController->controllersList->{$this->_controller}->indexAction)) ? $configController->controllersList->{$this->_controller}->indexAction : $this->_indexAction;
            else
                $this->_action = $action;

            //CallMethods
            $this->_callMethods = (isset($configController->controllersList->{$this->_controller}->callMethods)) ? $configController->controllersList->{$this->_controller}->callMethods : null;

            //Désactive autoCallIndexAction
            if (isset($configController->controllersList->{$this->_controller}['autoCallIndexAction']) && !(int) $configController->controllersList->{$this->_controller}['autoCallIndexAction'])
                $this->_autoCallIndexAction = false;
            //Désactive autoCallDisplay
            if (isset($configController->controllersList->{$this->_controller}['autoCallDisplay']) && !(int) $configController->controllersList->{$this->_controller}['autoCallDisplay'])
                $this->_autoCallDisplay = false;
            //Désactive l'implementation obligatoire d'une interface
            if (isset($configController->controllersList->{$this->_controller}['mustBeImplementInterface']) && !(int) $configController->controllersList->{$this->_controller}['mustBeImplementInterface'])
                $this->_interface['mustBeImplementInterface'] = false;
            if (isset($configController->controllersList->{$this->_controller}['interfaceName']))
                $this->_interface['interfaceName'] = (string) $configController->controllersList->{$this->_controller}['interfaceName'];

            $this->_isConfiguredController = true;
        } else
            $this->_isConfiguredController = false;
    }

    private function _mergeCallMethods($callMethods1, $callMethods2) {
        //TODO merge les callMethodes
    }

    private function _checkingController($controllerDir, $controller, $action) {
        $controllerClass = ($controllerDir == '') ? $this->_controllersNamespace . '\\' . $controller : $this->_controllersNamespace . '\\' . $controllerDir . '\\' . $controller;
        if (!class_exists($controllerClass)) {
            if ($this->_is404Controller)
                throw new \Exception('Controller 404 failed, la config du controller 404 est invalid : le controller "' . $controllerDir . '\\' . $controller . '" n\'existe pas');
            $this->_showController404('Le controller "' . $controllerClass . '" n\'existe pas');
            return false;
        }
        /*
          if (!$this->_is404Controller) {
          // loading setting configuration (constantes and var)
          if (file_exists($iniConfController = $this->_controllersDir . $controllerDir . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'setting.ini'))
          Config::loadIniFile($iniConfController);
          //Si le controller est desactivé..
          if (Config::isDefinedCons('DISABLE_CONTROLLER') && (stripos(Config::getCons('DISABLE_CONTROLLER'), $controller)) !== false) {
          $this->_showController404('Le controller "' . $controllerClass . '" est désactivé');
          return false;
          }
          } */

        $inst = new \ReflectionClass($controllerClass);
        if ($inst->isInterface())
            throw new \Exception('Controller "' . $controllerClass . '" ne peut être une interface');
        if ($inst->isAbstract())
            throw new \Exception('Controller "' . $controllerClass . '" ne peut être une classe abstraite');

        if ($this->_interface['mustBeImplementInterface']) {
            if (!interface_exists($this->_interface['interfaceName']))
                throw new \Exception('L\'interface ' . $this->_interface['interfaceName'] . ' que doit implémenter le controller "' . $controllerClass . '" n\'existe pas');
            if (!in_array($this->_interface['interfaceName'], $inst->getInterfaceNames()))
                throw new \Exception('Le controller "' . $controllerClass . '"  doit implementer l\'interface ' . $this->_interface['interfaceName'] . '');
        }

        if ($this->_autoCallDisplay && !$inst->hasMethod('display'))
            throw new \Exception('Le controller "' . $controllerClass . '" doit implementer la methode "Display"');

        if (!$inst->hasMethod($action)) {
            if ($this->_is404Controller)
                throw new \Exception('Controller 404 failed, la config du controller 404 est invalid : l\'action "' . $action . '" dans le controller "' . $controllerClass . '" n\'existe pas');
            $this->_showController404('L\'action: "' . $action . '" du controller "' . $controllerClass . '" n\'existe pas');
            return false;
        }
        return $inst;
    }

    private function _showController404($message) {
        if ($this->_config->urlController404) {
            $this->_is404Controller = true;
            $this->_runController($this->_config->urlController404->controllerDir, $this->_config->urlController404->controller, $this->_config->urlController404->action, true, $this->_config->urlController404->callMethods, array('message' => $message));
        } else {
            if (!headers_sent()) {
                header('HTTP/1.0 404 Not Found');
                header('Status: 404 Not Found');
            }
        }
    }

}

?>